/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.cjj;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.Component;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.render.*;
import ohos.agp.utils.Color;
import ohos.agp.utils.Rect;
import ohos.agp.utils.RectFloat;
import ohos.agp.window.service.Display;
import ohos.agp.window.service.DisplayAttributes;
import ohos.agp.window.service.DisplayManager;
import ohos.app.Context;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

public class MaterialProgressDrawable extends ShapeElement {
    // Maps to ProgressBar.Large style
    public static final int LARGE = 0;

    // Maps to ProgressBar default style
    public static final int DEFAULT = 1;
    // Maps to ProgressBar default style
    private static final int CIRCLE_DIAMETER = 40;
    private static final float CENTER_RADIUS = 8.75f; //should add up to 10 when + stroke_width
    private static final float STROKE_WIDTH = 2.5f;
    // Maps to ProgressBar.Large style
    private static final int CIRCLE_DIAMETER_LARGE = 56;
    private static final float CENTER_RADIUS_LARGE = 12.5f;
    static final float STROKE_WIDTH_LARGE = 3f;
    /**
     * The duration of a single progress spin in milliseconds.
     */
    private static final int ANIMATION_DURATION = 1000 * 80 / 60;
    /**
     * The number of points in the progress "star".
     */
    private static final float NUM_POINTS = 5f;
    /**
     * Layout info for the arrowhead in dp
     */
    private static final int ARROW_WIDTH = 10;
    private static final int ARROW_HEIGHT = 5;
    private static final float ARROW_OFFSET_ANGLE = 0;
    /**
     * Layout info for the arrowhead for the large spinner in dp
     */
    static final int ARROW_WIDTH_LARGE = 12;
    static final int ARROW_HEIGHT_LARGE = 6;
    private static final float MAX_PROGRESS_ARC = .8f;
    private final int[] COLORS = new int[]{
            Color.BLACK.getValue()
    };
    /**
     * The list of animators operating on this drawable.
     */
    private final ArrayList<Animator> mAnimators = new ArrayList<Animator>();
    /**
     * The indicator ring, used to manage animation state.
     */
    private final Ring mRing;
    private final OnChangeListener mCallback = new OnChangeListener() {
        @Override
        public int hashCode() {
            return super.hashCode();
        }

        @Override
        public void onChange(Element element) {

        }

    };
    private float mRotation;
    private Display mResources;
    private double mWidth;
    private double mHeight;
    private boolean mShowArrowOnFirstStart = false;
    List<Color> colorList = new ArrayList<>();
    private Callback callback;
    private AnimatorValue rotationAnimator;

    public MaterialProgressDrawable(Context context, Component animExcutor) {
        mResources = DisplayManager.getInstance().getDefaultDisplay(context).get();
        colorList.add(Color.RED);
        colorList.add(Color.BLUE);
        colorList.add(Color.YELLOW);
        mRing = new Ring(mCallback);
        mRing.setColors(COLORS);
        updateSizes(DEFAULT);
//        setupAnimators();
        setAnimators();
    }


    public void setCallback(Callback callback) {
        this.callback = callback;
    }

    public void setSizeParameters(double progressCircleWidth, double progressCircleHeight,
                                  double centerRadius, double strokeWidth, float arrowWidth, float arrowHeight) {
        final Ring ring = mRing;
        mWidth = progressCircleWidth;
        mHeight = progressCircleHeight;
        ring.setStrokeWidth((float) strokeWidth);
        ring.setCenterRadius(centerRadius);
        ring.setColorIndex(0);
        ring.setArrowDimensions(arrowWidth, arrowHeight);
        ring.setInsets((int) mWidth, (int) mHeight);
    }

    /**
     * Set the overall size for the progress spinner. This updates the radius
     * and stroke width of the ring.
     *
     * @param size 类型size
     */
    public void updateSizes(@ProgressDrawableSize int size) {
        final DisplayAttributes metrics = mResources.getAttributes();
        final float screenDensity = metrics.scalDensity;

        if (size == LARGE) {
            setSizeParameters(CIRCLE_DIAMETER_LARGE * screenDensity, CIRCLE_DIAMETER_LARGE * screenDensity, CENTER_RADIUS_LARGE * screenDensity,
                    STROKE_WIDTH_LARGE * screenDensity, ARROW_WIDTH_LARGE * screenDensity, ARROW_HEIGHT_LARGE * screenDensity);
        } else {
            setSizeParameters(CIRCLE_DIAMETER * screenDensity, CIRCLE_DIAMETER * screenDensity, CENTER_RADIUS * screenDensity, STROKE_WIDTH * screenDensity,
                    ARROW_WIDTH * screenDensity, ARROW_HEIGHT * screenDensity);
        }
    }

    /**
     * progressbar是否显示箭头
     *
     * @param show Set to true to display the arrowhead on the progress spinner.
     */
    public void showArrow(boolean show) {
        mRing.setShowArrow(show);
    }

    /**
     * 设置箭头大小
     *
     * @param scale Set the scale of the arrowhead for the spinner.
     */
    public void setArrowScale(float scale) {
        mRing.setArrowScale(scale);
    }

    /**
     * Set the start and end trim for the progress spinner arc.
     *
     * @param startAngle start angle
     * @param endAngle   end angle
     */
    public void setStartEndTrim(float startAngle, float endAngle) {
        mRing.setStartTrim(startAngle);
        mRing.setEndTrim(endAngle);
    }

    /**
     * Set the amount of rotation to apply to the progress spinner.
     *
     * @param rotation Rotation is from [0..1]
     */
    public void setProgressRotation(float rotation) {
        mRing.setRotation(rotation);
    }

    /**
     * Update the background color of the circle image view.
     *
     * @param color 背景色
     */
    public void setBackgroundColor(int color) {
        mRing.setBackgroundColor(color);
    }

    /**
     * Set the colors used in the progress animation from color resources.
     * The first color will also be the color of the bar that grows in response
     * to a user swipe gesture.
     *
     * @param colors 颜色值
     */
    public void setColorSchemeColors(int... colors) {
        mRing.setColors(colors);
        mRing.setColorIndex(0);
    }

//    @Override
//    public int getIntrinsicHeight() {
//        return (int) mHeight;
//    }
//
//    @Override
//    public int getIntrinsicWidth() {
//        return (int) mWidth;
//    }

    @Override
    public void drawToCanvas(Canvas canvas) {
//        super.drawToCanvas(canvas);
        final Rect bounds = getBounds();
        final int saveCount = canvas.save();
//        mRotation++;
//        if (mRotation >= 360) mRotation = 0;
        canvas.rotate(mRotation, bounds.getCenterX(), bounds.getCenterY());

//        Paint paint = new Paint();
//        paint.setColor(new Color(0xff000000));
//        paint.setTextSize(20);
//        canvas.drawText(paint, "aaaaa", 35, 65);
        mRing.draw(canvas, bounds);
        canvas.restoreToCount(saveCount);
        System.out.println("drawToCanvas-------------" + bounds + " " + mRotation);
    }

    public int getAlpha() {
        return mRing.getAlpha();
    }

    @Override
    public void setAlpha(int alpha) {
        mRing.setAlpha(alpha);
    }

    //    @Override
//    public void setColorFilter(ColorFilter colorFilter) {
//        mRing.setColorFilter(matrix);
//    }

    @SuppressWarnings("unused")
    private float getRotation() {
        return mRotation;
    }

    @SuppressWarnings("unused")
    void setRotation(float rotation) {
        mRotation = rotation;
        invalidateSelf();
    }


    private void setAnimators() {
        rotationAnimator = new AnimatorValue();
        rotationAnimator.setDuration(ANIMATION_DURATION);
        rotationAnimator.setLoopedCount(-1);
        rotationAnimator.setValueUpdateListener((animatorValue, v) -> {
            setProgressRotation(v);
        });
//        rotationAnimator.setLoopedListener(new Animator.LoopedListener() {
//            @Override
//            public void onRepeat(Animator animator) {
//                mRing.storeOriginals();
//                mRing.goToNextColor();
//                mRing.setStartTrim(mRing.getEndTrim());
//                if (mFinishing) {
//                    // finished closing the last ring from the swipe gesture; go
//                    // into progress mode
//                    mFinishing = false;
//                    rotationAnimator.setDuration(ANIMATION_DURATION);
//                    mRing.setShowArrow(false);
//                } else {
//                    mRotationCount = (mRotationCount + 1) % (NUM_POINTS);
//                }
//            }
//        });
    }

    public void onStop() {
        if (rotationAnimator != null) {
            rotationAnimator.cancel();
            setRotation(0);
            mRing.setShowArrow(false);
            mRing.setColorIndex(0);
            mRing.resetOriginals();
        }

    }

    public void onStart() {
        rotationAnimator.start();
        mRing.setShowArrow(mShowArrowOnFirstStart);
    }

    public void showArrowOnFirstStart(boolean showArrowOnFirstStart) {
        this.mShowArrowOnFirstStart = showArrowOnFirstStart;
    }

    public @interface ProgressDrawableSize {
    }

    private class Ring {
        private final RectFloat mTempBounds = new RectFloat();
        private final Paint mPaint = new Paint();
        private final Paint mArrowPaint = new Paint();

        private final OnChangeListener mCallback;
        private final Paint mCirclePaint = new Paint();
        private float mStartTrim = 0.0f;
        private float mEndTrim = 0.0f;
        private float mRotation = 0.0f;
        private float mStrokeWidth = 5.0f;
        private float mStrokeInset = 2.5f;
        // mColorIndex represents the offset into the available mColors that the
        // progress circle should currently display. As the progress circle is
        // animating, the mColorIndex moves by one to the next available color.
        private int mColorIndex;
        private float mStartingStartTrim;
        private float mStartingEndTrim;
        private float mStartingRotation;
        private boolean mShowArrow;
        private Path mArrow;
        private float mArrowScale;
        private double mRingCenterRadius;
        private int mArrowWidth;
        private int mArrowHeight;
        private int mAlpha;
        private int mBackgroundColor = Color.WHITE.getValue();

        public Ring(OnChangeListener callback) {
            mCallback = callback;

//            mPaint.setStrokeCap(Paint.StrokeCap.SQUARE_CAP);
            mPaint.setAntiAlias(true);
            mPaint.setStyle(Paint.Style.STROKE_STYLE);
            mArrowPaint.setStyle(Paint.Style.FILL_STYLE);
            mArrowPaint.setAntiAlias(true);
        }

        public void setBackgroundColor(int color) {
            mBackgroundColor = color;
        }

        /**
         * Set the dimensions of the arrowhead.
         *
         * @param width  Width of the hypotenuse of the arrow head
         * @param height Height of the arrow point
         */
        public void setArrowDimensions(float width, float height) {
            mArrowWidth = (int) width;
            mArrowHeight = (int) height;
        }

        /**
         * Draw the progress spinner
         *
         * @param c      画布
         * @param bounds 矩形rect
         */
        public void draw(Canvas c, Rect bounds) {
            final RectFloat arcBounds = mTempBounds;
            arcBounds.modify(bounds);
//            arcBounds.translate(mStrokeInset, mStrokeInset);
            arcBounds.left = new BigDecimal(arcBounds.left).add(new BigDecimal(mStrokeInset)).floatValue();
            arcBounds.top = new BigDecimal(arcBounds.top).add(new BigDecimal(mStrokeInset)).floatValue();
            arcBounds.right = new BigDecimal(arcBounds.right).subtract(new BigDecimal(mStrokeInset)).floatValue();
            arcBounds.bottom = new BigDecimal(arcBounds.bottom).subtract(new BigDecimal(mStrokeInset)).floatValue();

            final float startAngle = new BigDecimal(mStartTrim).add(new BigDecimal(mRotation)).floatValue() * 360;
            final float endAngle = new BigDecimal(mEndTrim).add(new BigDecimal(mRotation)).floatValue() * 360;
            float sweepAngle = new BigDecimal(endAngle).subtract(new BigDecimal(startAngle)).floatValue();
            System.out.println(startAngle + "--a--" + endAngle + "----" + mStartTrim + "---" + mEndTrim + "---" + mRotation);

            mPaint.setColor(Color.RED);
            c.drawArc(arcBounds, new Arc(startAngle, sweepAngle, false), mPaint);
//            c.drawColor(colorList.get(mColorIndex).getValue(), BlendMode.SRC);
            System.out.println(arcBounds + "---" + startAngle + "---" + sweepAngle);

            drawTriangle(c, startAngle, sweepAngle, bounds);

            if (mAlpha < 255) {
                mCirclePaint.setColor(new Color(mBackgroundColor));
                mCirclePaint.setAlpha(255 - mAlpha);
                c.drawCircle(bounds.getCenterX(), bounds.getCenterY(), bounds.getWidth() / (float) 2,
                        mCirclePaint);
            }
        }

        private void drawTriangle(Canvas c, float startAngle, float sweepAngle, Rect bounds) {
            if (mShowArrow) {
                if (mArrow == null) {
                    mArrow = new Path();
                    mArrow.setFillType(Path.FillType.EVEN_ODD);
                } else {
                    mArrow.reset();
                }

                // Adjust the position of the triangle so that it is inset as
                // much as the arc, but also centered on the arc.
                float x = (float) (mRingCenterRadius * Math.cos(0) + bounds.getCenterX());
                float y = (float) (mRingCenterRadius * Math.sin(0) + bounds.getCenterY());

                // Update the path each time. This works around an issue in SKIA
                // where concatenating a rotation matrix to a scale matrix
                // ignored a starting negative rotation. This appears to have
                // been fixed as of API 21.
                mArrow.moveTo(0, 0);
                mArrow.lineTo((mArrowWidth) * mArrowScale, 0);
                mArrow.lineTo(((mArrowWidth) * mArrowScale / 2), (mArrowHeight
                        * mArrowScale));
                mArrow.offset(new BigDecimal(x).subtract(new BigDecimal((mArrowWidth) * mArrowScale / 2)).floatValue(), y);
                mArrow.close();
                // draw a triangle
//                mArrowPaint.setColor(colorList.get(mColorIndex));
                mArrowPaint.setColor(Color.RED);
                //when sweepAngle < 0 adjust the position of the arrow
                c.rotate(new BigDecimal(startAngle).add(new BigDecimal(sweepAngle < 0 ? 0 : sweepAngle)).subtract(new BigDecimal(ARROW_OFFSET_ANGLE)).floatValue(), bounds.getCenterX(),
                        bounds.getCenterY());
                c.drawPath(mArrow, mArrowPaint);
            }
        }

        /**
         * Set the colors the progress spinner alternates between.
         *
         * @param colors Array of integers describing the colors. Must be non-<code>null</code>.
         */
        public void setColors(int[] colors) {
            // if colors are reset, make sure to reset the color index as well
            setColorIndex(0);
        }

        /**
         * 设置progressbar的颜色
         *
         * @param index Index into the color array of the color to display in
         *              the progress spinner.
         */
        public void setColorIndex(int index) {
            mColorIndex = index;
        }

        /**
         * Proceed to the next available ring color. This will automatically
         * wrap back to the beginning of colors.
         */
        public void goToNextColor() {
//            mColorIndex = (mColorIndex + 1) % (mColors.length);
            mColorIndex = (mColorIndex + 1) % (colorList.size());
        }

        public void setColorFilter(ColorFilter filter) {
            mPaint.setColorFilter(filter);
            invalidateSelf();
        }

        /**
         * 获取alpha值
         *
         * @return Current alpha of the progress spinner and arrowhead.
         */
        public int getAlpha() {
            return mAlpha;
        }

        /**
         * 设置alpha值
         *
         * @param alpha Set the alpha of the progress spinner and associated arrowhead.
         */
        public void setAlpha(int alpha) {
            mAlpha = alpha;
        }

        @SuppressWarnings("unused")
        public float getStrokeWidth() {
            return mStrokeWidth;
        }

        /**
         * 设置画笔宽度
         *
         * @param strokeWidth Set the stroke width of the progress spinner in pixels.
         */
        public void setStrokeWidth(float strokeWidth) {
            mStrokeWidth = strokeWidth;
            mPaint.setStrokeWidth(strokeWidth);
            invalidateSelf();
        }

        @SuppressWarnings("unused")
        public float getStartTrim() {
            return mStartTrim;
        }

        @SuppressWarnings("unused")
        public void setStartTrim(float startTrim) {
            mStartTrim = startTrim;
            invalidateSelf();
        }

        public float getStartingStartTrim() {
            return mStartingStartTrim;
        }

        public float getStartingEndTrim() {
            return mStartingEndTrim;
        }

        @SuppressWarnings("unused")
        public float getEndTrim() {
            return mEndTrim;
        }

        @SuppressWarnings("unused")
        public void setEndTrim(float endTrim) {
            mEndTrim = endTrim;
            invalidateSelf();
        }

        @SuppressWarnings("unused")
        public float getRotation() {
            return mRotation;
        }

        @SuppressWarnings("unused")
        public void setRotation(float rotation) {
            mRotation = rotation;
            invalidateSelf();
        }

        public void setInsets(int width, int height) {
            final float minEdge = (float) Math.min(width, height);
            float insets;
            if (mRingCenterRadius <= 0 || minEdge < 0) {
                insets = (float) Math.ceil(mStrokeWidth / 2.0f);
            } else {
                insets = (float) (minEdge / 2.0f - mRingCenterRadius);
            }
            mStrokeInset = insets;
        }

        @SuppressWarnings("unused")
        public float getInsets() {
            return mStrokeInset;
        }

        public double getCenterRadius() {
            return mRingCenterRadius;
        }

        /**
         * 设置圆半径
         *
         * @param centerRadius Inner radius in px of the circle the progress
         *                     spinner arc traces.
         */
        public void setCenterRadius(double centerRadius) {
            mRingCenterRadius = centerRadius;
        }

        /**
         * 设置是否展示箭头
         *
         * @param show Set to true to show the arrow head on the progress spinner.
         */
        public void setShowArrow(boolean show) {
            if (mShowArrow != show) {
                mShowArrow = show;
                invalidateSelf();
            }
        }

        /**
         * 设置箭头大小
         *
         * @param scale Set the scale of the arrowhead for the spinner.
         */
        public void setArrowScale(float scale) {
            if (scale != mArrowScale) {
                mArrowScale = scale;
                invalidateSelf();
            }
        }

        /**
         * progress spinner is currently rotated
         *
         * @return The amount the progress spinner is currently rotated, between [0..1].
         */
        public float getStartingRotation() {
            return mStartingRotation;
        }

        /**
         * If the start / end trim are offset to begin with, store them so that
         * animation starts from that offset.
         */
        public void storeOriginals() {
            mStartingStartTrim = mStartTrim;
            mStartingEndTrim = mEndTrim;
            mStartingRotation = mRotation;
        }

        /**
         * Reset the progress spinner to default rotation, start and end angles.
         */
        public void resetOriginals() {
            mStartingStartTrim = 0;
            mStartingEndTrim = 0;
            mStartingRotation = 0;
            setStartTrim(0);
            setEndTrim(0);
            setRotation(0);
        }
    }

    public void invalidateSelf() {
        if (callback != null) callback.onRefresh();
    }

    public interface Callback {
        void onRefresh();
    }
}
